﻿
using DtoFw.Base;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;

namespace Cache
{

    public enum TokenTypeEnum
    {
        AccessToken = 1,
        RefreshToken = 2
    }

    public class TokenHelper
    {
        public static TokenData LocalToken
        {
            get;
            set;
        }
        /// <summary>
        /// 登录生成tokentoken
        /// </summary>
        /// <param name="data">存储内容</param>
        /// <returns></returns>
        public static string CreateToken(TokenData data, TokenTypeEnum tokenType)
        {
            if (data != null)
            {
                List<Claim> list = new List<Claim>();
                list.Add(new Claim(JwtRegisteredClaimNames.Nbf, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"));
                list.Add(new Claim(JwtRegisteredClaimNames.Exp, $"{new DateTimeOffset(DateTime.Now.AddMinutes(GlobalData.JwtConfig.ExpiresMinutes)).ToUnixTimeSeconds()}"));
                list.Add(new Claim("UserId", data.UserId.ToString()));
                list.Add(new Claim("UserNo", data.UserNo));
                list.Add(new Claim("UserName", data.UserName));
                list.Add(new Claim("RoleId", data.RoleId.ToString()));
                list.Add(new Claim("OrganId", data.OrganId.ToString()));
                list.Add(new Claim("OrganName", data.OrganName));
                list.Add(new Claim("Mobile", data.Mobile));
                list.Add(new Claim("ComNo", data.ComNo));
                list.Add(new Claim("DeptId", data.DeptId.ToString()));
                return CreateTokenByHandler(list, tokenType);
            }
            else
                return null;
        }

        /// <summary>
        /// 创建jwtToken,采用微软内部方法，默认使用HS256加密，如果需要其他加密方式，请更改源码
        /// 返回的结果和CreateToken一样
        /// </summary>
        /// <param name="payLoad"></param>
        /// <param name="expiresMinute">有效分钟</param>
        /// <returns></returns>
        public static string CreateTokenByHandler(List<Claim> payLoad,
            TokenTypeEnum tokenType)
        {
            var now = DateTime.UtcNow.AddHours(8);
            var claims = payLoad;
            var jwt = new JwtSecurityToken(
                issuer: GlobalData.JwtConfig.Issuer,
                audience: tokenType == TokenTypeEnum.AccessToken ? GlobalData.JwtConfig.Audience : GlobalData.JwtConfig.RefreshTokenAudience,
                expires: tokenType == TokenTypeEnum.AccessToken ? DateTime.Now.AddMinutes(GlobalData.JwtConfig.ExpiresMinutes) : DateTime.Now.AddMinutes(GlobalData.JwtConfig.RefreshTokenExpiresMinutes),
                claims: claims,
                signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(GlobalData.JwtConfig.SignKey)), SecurityAlgorithms.HmacSha256));
            var token = new JwtSecurityTokenHandler().WriteToken(jwt);
            return token;
        }

        /// <summary>
        /// 验证身份 验证签名的有效性,
        /// </summary>
        /// <param name="encodeJwt"></param>
        /// <param name="validatePayLoad">自定义各类验证； 是否包含那种申明，或者申明的值， </param>
        /// 例如：payLoad["aud"]?.ToString() == "roberAuddience";
        /// 例如：验证是否过期 等
        /// <returns></returns>
        public static bool Validate(string encodeJwt, Func<Dictionary<string, object>, bool> validatePayLoad = null)
        {
            var success = true;
            var jwtArr = encodeJwt.Split('.');
            var header = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[0]));
            var payLoad = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[1]));

            var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(GlobalData.JwtConfig.SignKey));
            //首先验证签名是否正确（必须的）
            success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1])))));
            if (!success)
            {
                return success;//签名不正确直接返回
            }
            //其次验证是否在有效期内（也应该必须）
            var now = ToUnixEpochDate(DateTime.UtcNow.AddHours(8));
            success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString()));
            if (validatePayLoad != null)
                success = validatePayLoad(payLoad);

            return success;
        }

        /// <summary>
        /// 获取jwt中的payLoad
        /// </summary>
        /// <param name="encodeJwt"></param>
        /// <returns></returns>
        public static Dictionary<string, object> GetPayLoad(string encodeJwt)
        {
            var jwtArr = encodeJwt.Split('.');
            var payLoad = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[1]));
            return payLoad;
        }

        public static long ToUnixEpochDate(DateTime date) =>
            (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);
        /// <summary>
        /// 获取token数值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="claims"></param>
        /// <param name="t"></param>
        /// <returns></returns>
        public static T GetTokenInfo<T>(ClaimsPrincipal claims) where T : new()
        {
            T t = new T();
            var properties = t.GetType().GetProperties();
            foreach (System.Reflection.PropertyInfo item in properties)
            {
                var result = claims.FindFirst(l => l.Type == item.Name);
                if (result == null || string.IsNullOrEmpty(result.Value))
                    continue;
                item.SetValue(t, Convert.ChangeType(result.Value, item.PropertyType));//反射转为对象为指定类型
            }
            return t;
        }
        /// <summary>
        /// 获取token数值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="claims"></param>
        /// <param name="t"></param>
        /// <returns></returns>
        public static T GetTokenInfo<T>(IEnumerable<Claim> claims) where T : new()
        {
            T t = new T();
            var properties = t.GetType().GetProperties();
            foreach (System.Reflection.PropertyInfo item in properties)
            {
                var result = claims.FirstOrDefault(l => l.Type == item.Name);
                if (result == null || string.IsNullOrEmpty(result.Value))
                    continue;
                item.SetValue(t, result.Value, null);
            }
            return t;
        }
        /// <summary>
        /// 从Token中获取用户身份
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public static ClaimsPrincipal GetPrincipalFromAccessToken(string token)
        {
            var handler = new JwtSecurityTokenHandler();
            try
            {
                return handler.ValidateToken(token, new TokenValidationParameters
                {
                    ValidateAudience = false,
                    ValidateIssuer = false,
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(GlobalData.JwtConfig.SignKey)),
                    ValidateLifetime = false
                }, out SecurityToken validatedToken);
            }
            catch (Exception)
            {
                return null;
            }
        }
        public static string RefreshToken(ClaimsPrincipal claimsPrincipal, TokenData token)
        {
            var code = claimsPrincipal.Claims.FirstOrDefault(m => m.Type.Equals(ClaimTypes.NameIdentifier));
            if (null != code)
                return CreateToken(token, TokenTypeEnum.AccessToken);
            else
                return "";
        }
    }
}
